# -*- coding: utf-8 -*-

CONFIG = ""

FONT_SIZE = 20
FONT_NAME = 'DejaVuSansMono.ttf'

FONT_COLOR = "black"
NODE_FILL_COLOR = "#DDDDDD"
NODE_OUTLINE_COLOR = "black"
LINES_COLOR = "black"
BACKGROUND_COLOR = "white"
CHECKED_NODE_COLOR = "black"
CHECKED_FONT_COLOR = "#DDDDDD"

NODES_MARGIN = 8
WIDTH_MARGIN = 10
HEIGTH_MARGIN = 25

DEFAULT_INPUT_FILENAME = "tree.txt" #stdin
DEFAULT_RESULT_FILENAME = "tree.png"

AUTHOR_INFO = "bintree_view 0.1 (c) Mingalev Oleg, 2012"

def print_help():
    print AUTHOR_INFO
    print u"""
Ключи командной строки
--help: Показать эту справку
--input-format: Показать справку по формату двоичных деревьев
--font-size: Размер шрифта
--nodes-margin: Отступы от текста в вершинах
--width-margin: Горизонтальные отступы между вершинами
--height-margin: Вертикальные отступы между вершинами
--font-name: Путь до TTF шрифта
--font-color: Цвет текста
--node-fill-color: Цвет заливки вершин
--node-outline-color: Цвет границ вершин
--lines-color: Цвет рёбер
--background-color: Цвет фона
--checked-node-color: Цвет фона выделенных вершин
--checked-font-color: Цвет текста в выделенных вершинах
--input: Входной файл или stdin для ввода через стандартных поток
--result: Результирующий файл в формате PNG
"""

def print_input_format():
    print AUTHOR_INFO
    print u"""
Фомат задания двоичного дерева:

1. Пустое дерево задаётся строкой "#"
2. Непустое дерево задаётся следующим образом:
  [.!]<значение корня> (точка или восклицательный знак, затем, без пробела, значение в корне)
   Левое поддерево (ко всем строкам в начале добавлен пробел)
   Правое поддерево (ко всем строкам в начале добавлен пробел)

Вершины, отмеченые восклицательным знаком, считаются помечеными и рисуются другим цветом.

Пример:
.Путин
 !Медведев
  .Медведева
   #
   #
  #
 .Садовничий
  #
  !Андриянов
   .Сыркин
    #
    .Анси
     #
     #
   #
"""

from sys import argv

argv = argv[1:]

while argv:
    OK = False
    for option in (("--font-size", "FONT_SIZE"), ("--nodes-margin", "NODES_MARGIN"), 
            ("--width-margin", "WIDTH_MARGIN"), ("--height-margin", "HEIGHT_MARGIN")):
        if argv[0] == option[0]:
            argv = argv[1:]
            if not argv:
                exit('bintree_view: option "%s" require an argument' % option[0])
            globals()[option[1]] = int(argv[0])
            argv = argv[1:]
            OK = True
            break
    if OK: continue
    for option in (("--font-name", "FONT_NAME"), ("--font-color", "FONT_COLOR"), 
            ("--node-fill-color", "NODE_FILL_COLOR"), ("--node-outline-color", "NODE_OUTLINE_COLOR"),
            ("--lines-color", "LINES_COLOR"), ("--background-color", "BACKGROUND_COLOR"),
            ("--checked-node-color", "CHECKED_NODE_COLOR"), ("--checked-font-color", "CHECKED_FONT_COLOR"),
            ("--input", "DEFAULT_INPUT_FILENAME"), ("--result", "DEFAULT_RESULT_FILENAME"),
            ("--config", "CONFIG")):
        if argv[0] == option[0]:
            argv = argv[1:]
            if not argv:
                exit('bintree_view: option "%s" require an argument' % option[0])
            globals()[option[1]] = argv[0]
            argv = argv[1:]
            OK = True
            break
    if OK: continue
    if argv[0] == '--help':
        print_help()
        exit()
    if argv[0] == '--input-format':
        print_input_format()
        exit()
    if not OK:
        exit('tree_view: invalid option "%s"\nProblems? Use tree_view --help' % argv[0])

import Image, ImageDraw, ImageFont
FONT = ImageFont.truetype(FONT_NAME, FONT_SIZE)

class Node:
    def __init__(self, val):
        self.val = val
        self.l = None
        self.r = None
        self.p = None
        self.pos = None
        self.checked = False
    def size(self):
        text = self.val
        margin = NODES_MARGIN
        size = FONT.getsize(text)
        return size[0] + 2*margin, size[1] + 2*margin
    def __str__(self):
        a = "%s: [%s, %s]" % (self.val, self.l, self.r)
        return a
    def __unicode__(self):
        return self.__str__()
    def draw(self, drawer):
        text = self.val
        pos = self.pos
        margin = NODES_MARGIN
        size = FONT.getsize(self.val)
        pos = self.pos[0] + margin, self.pos[1] + margin
        fill = CHECKED_NODE_COLOR if self.checked else NODE_FILL_COLOR
        draw.rectangle((pos[0] - margin, pos[1] - margin, pos[0] + size[0] + margin, pos[1] + size[1] + margin), fill=fill, outline=NODE_OUTLINE_COLOR)
        fill = CHECKED_FONT_COLOR if self.checked else FONT_COLOR
        draw.text(pos, self.val, font=FONT, fill=fill)
    def center(self):
        return self.pos[0] + self.size()[0] // 2, self.pos[1] + self.size()[1] // 2

def generate_nodes(arr, parent = None):
    if not arr:
        return None
    ans = Node(arr[0])
    ans.p = parent
    ans.checked = bool(arr[1])
    ans.l = generate_nodes(arr[2], ans)
    ans.r = generate_nodes(arr[3], ans)
    return ans

cw = 0
HEIGHT = 0
def calc_width_and_height(node, wmargin = WIDTH_MARGIN, hmargin = HEIGTH_MARGIN):
    global cw, HEIGHT
    cw = 0

    if not node:
        size = FONT.getsize("Empty tree")
        size = size[0] + 2*wmargin, size[1] + 2*hmargin
        return size

    h1 = node.size()[1]
    HEIGHT = 0

    def dfs(node, depth = 0):
        if not node:
            return 0
        dfs(node.l, depth + 1)
        global HEIGHT, cw
        cw += wmargin
        node.pos = (cw, (hmargin + h1)*depth + hmargin)
        HEIGHT = node.pos[1] + node.size()[1] if node.pos[1] + node.size()[1] > HEIGHT else HEIGHT
        cw += node.size()[0]
        dfs(node.r, depth + 1)

    dfs(node)
    HEIGHT += hmargin
    WIDTH = cw + wmargin
    return(WIDTH, HEIGHT)

def nodes_draw(node, driver):
    def draw_lines(node):
        if not node:
            return
        if node.p:
            driver.line((node.center(), node.p.center()), width = 3, fill = LINES_COLOR)
        draw_lines(node.l)
        draw_lines(node.r)
        
    def draw_nodes(node):
        if not node:
            return
        node.draw(driver)
        draw_nodes(node.l)
        draw_nodes(node.r)
    if node:
        draw_lines(node)
        draw_nodes(node)
    else:
        driver.text((WIDTH_MARGIN, HEIGTH_MARGIN), "Empty tree", fill = FONT_COLOR, font=FONT)

def lines_to_array(lines):
    def lines_to_array_inside(lines):
        try:
            if lines[0][0] == '#':
                return None
        except:
            raise Exception("Wrong format")
        if lines[0][0] not in ('.', '!'):
            raise Exception("Wrong format")
        checked, name = lines[0][0] == '!', lines[0][1:]
        N = []
        for i in range(1, len(lines)):
            if (lines[i][0] == ' ') and (lines[i][1] != ' '):
                N.append(i)
        if len(N) != 2:
            raise Exception("Wrong format")
        l = lines_to_array_inside(list(map((lambda x: x[1:]), lines[N[0]:N[1]])))
        r = lines_to_array_inside(list(map((lambda x: x[1:]), lines[N[1]:])))
        return (name, checked, l, r)
    lines = list(map(lambda x: x.rstrip(), lines))
    lines = list(filter(None, lines))
    return lines_to_array_inside(lines)

if DEFAULT_INPUT_FILENAME != 'stdin':
    test = open(DEFAULT_INPUT_FILENAME).readlines()
else:
    test = __import__("sys").stdin.read().split('\n')

test = list(map(lambda x: unicode(x, 'utf-8'), test))

test = lines_to_array(test)
test = generate_nodes(test)

SIZE = calc_width_and_height(test)

SIZE = (SIZE[0], SIZE[1])

image = Image.new("RGBA", SIZE, (0,0,0,0))
draw = ImageDraw.Draw(image)
draw.rectangle(((0, 0), SIZE), fill=BACKGROUND_COLOR, outline=BACKGROUND_COLOR)
nodes_draw(test, draw)

image.save(DEFAULT_RESULT_FILENAME, "PNG")
